<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="A small website and API to show off server-side tracking technology, that also provides an API for TLS (ja3) )and http2 fingerprinting"
    />
    <meta
      name="keywords"
      content="ja3, ja3 fingerprinting api, tls fingerprinting, http2 fingerprinting, akamai fingerprinting, cloudflare fingerprinting, bypass ja3, spoof ja3"
    />
    <meta name="og:site_name" content="TrackMe - ja3 API" />
    <meta charset="utf-8" />
    <title>TrackMe | Explore</title>
    <script src="https://cdn.tailwindcss.com"></script>

    <style>
      * {
        transition-property: all;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
        transition-duration: 150ms;
      }
    </style>
  </head>

  <body class="m-5 dark:bg-gray-800 dark:text-gray-50">
    <h1 class="text-4xl mb-3 font-black">
      <a href="/">TrackMe</a> - Explore fingerprints
    </h1>

    <div id="search-box">
      <h2 class="text-2xl pt-5" id="search-title">
        Exploring
        <span id="fp-type"
          ><select
            class="dark:bg-gray-700 rounded shadow p-1 dark:text-gray-50"
            id="fp-type-dd"
          >
            <option value="h2">HTTP/2 fingerprint</option>
            <option value="ja3">JA3 fingerprint (TLS)</option>
            <option value="peetprint">Custom "PeetPrint" (TLS)</option>
            <option value="peetprint">User-Agent</option>
          </select></span
        >
      </h2>

      <p class="my-2">
        Query:
        <input
          class="dark:bg-gray-700 rounded shadow p-1 dark:text-gray-50"
          id="input"
          autocomplete="off"
        />
        <span class="dark:text-red-400">(Full fingerprint, no hashes)</span>
      </p>

      <button
        onclick="search()"
        class="dark:bg-gray-900 bg-gray-200 p-3 my-3 rounded-xl shadow"
      >
        Search
      </button>
    </div>

    <div id="result-box">
      <h2 class="text-2xl pt-5" id="result-title">
        Exploring
        <span id="fp-type-name"></span>
      </h2>

      <h2 class="text-3xl my-2">
        Results for
        <input
          class="dark:bg-gray-700 rounded shadow dark:text-gray-50"
          id="query"
        />:
      </h2>
      <hr class="pb-5" />
      <div class="text-xs" id="result-data">
        <div class="flex flex-wrap">
          <div
            class="bg-gray-100 dark:bg-gray-900 rounded-md shadow p-3 m-3 hover:bg-gray-50 dark:hover:bg-black min-w-fit grow"
          >
            <table class="table-auto" id="ja3s">
              <thead>
                <tr>
                  JA3s
                </tr>
              </thead>
            </table>
          </div>
          <div
            class="bg-gray-100 dark:bg-gray-900 rounded-md shadow p-3 m-3 hover:bg-gray-50 dark:hover:bg-black min-w-fit grow"
          >
            <table class="table-auto" id="h2-fps">
              <thead>
                <tr>
                  HTTP/2 fingerprints
                </tr>
              </thead>
            </table>
          </div>
          <div
            class="bg-gray-100 dark:bg-gray-900 rounded-md shadow p-3 m-3 hover:bg-gray-50 dark:hover:bg-black min-w-fit"
          >
            <table class="table-auto" id="peetprints">
              <thead>
                <tr>
                  PeetPrints
                </tr>
              </thead>
            </table>
          </div>
          <div
            class="bg-gray-100 dark:bg-gray-900 rounded-md shadow p-3 m-3 hover:bg-gray-50 dark:hover:bg-black min-w-fit grow"
          >
            <table class="table-auto" id="user-agents">
              <thead>
                <tr>
                  User-Agens
                </tr>
              </thead>
            </table>
          </div>
        </div>
      </div>
      <p class="text-2xl" id="result-info"></p>
    </div>

    <script>
      const sortByVal = (dict) => {
        var items = Object.keys(dict).map(function (key) {
          return [key, dict[key]];
        });

        // Sort the array based on the second element
        items.sort(function (first, second) {
          return second[1] - first[1];
        });
        return items;
      };

      const search = () => {
        const type = document.getElementById('fp-type-dd').value;
        const query = document.getElementById('input').value;
        if (!type || !query) {
          alert("Type and query can't be empty");
        }
        window.location.href = `/explore?by=${encodeURIComponent(
          query
        )}&type=${type}`;
      };

      // https://stackoverflow.com/a/5448635
      const getSearchParameters = () => {
        let prmstr = window.location.search.substr(1);
        return prmstr != null && prmstr !== ''
          ? transformToAssocArray(prmstr)
          : {};
      };

      const transformToAssocArray = (prmstr) => {
        let params = {};
        let prmarr = prmstr.split('&');
        for (let i = 0; i < prmarr.length; i++) {
          let tmparr = prmarr[i].split('=');
          params[tmparr[0]] = tmparr[1];
        }
        return params;
      };

      const isEmpty = (obj) => {
        try {
          return Object.keys(obj).length === 0;
        } catch (e) {
          return true;
        }
      };

      const searchTypes = ['h2', 'ja3', 'peetprint', 'useragent'];

      const readable = {
        h2: 'HTTP/2 fingerprint',
        ja3: 'JA3 fingerprint (TLS)',
        peetprint: 'Custom "PeetPrint" (TLS)',
        useragent: 'User-Agent',
      };

      const insert = () => {
        const params = getSearchParameters();

        if (!searchTypes.includes(params.type) || !params.by) {
          document.getElementById('result-box').style.display = 'none';
          // alert('Cant query', params.type);
          let html = `<select class="dark:bg-gray-700 rounded shadow p-1 dark:text-gray-50" id="fp-type-dd">`;

          for (const [k, v] of Object.entries(readable)) {
            html += `<option value="${k}">${v}</option>`;
          }
          html += '</select>';
          document.getElementById('fp-type').innerHTML = html;
          document.getElementById('input').innerHTML = `<p class="my-2">Query:
              <input
              class="dark:bg-gray-700 rounded shadow p-1 dark:text-gray-50"
              id="input" autocomplete="off" />
              <span class="dark:text-red-400">(Full fingerprint, no hashes)</span></p>`;
        } else {
          document.getElementById('search-box').style.display = 'none';
          document.getElementById('result-box').style.display = 'block';
          document.getElementById('query').value = decodeURIComponent(
            params.by
          );
          document.getElementById('fp-type-name').innerHTML =
            readable[params.type];

          fetch(
            `/api/search-${params.type}?by=${encodeURIComponent(params.by)}`
          )
            .then((res) => res.json())
            .then((data) => {
              if (
                isEmpty(data.h2_fps) &&
                isEmpty(data.ja3s) &&
                isEmpty(data.PeetPrint) &&
                isEmpty(data.UserAgents)
              ) {
                document.getElementById('result-data').style.display = 'none';
                document.getElementById('result-info').innerText = 'No results';
              } else {
                const mapping = [
                  {
                    notParamType: 'h2',
                    dataKey: 'h2_fps',
                    elemID: 'h2-fps',
                  },
                  {
                    notParamType: 'ja3',
                    dataKey: 'ja3s',
                    elemID: 'ja3s',
                  },
                  {
                    notParamType: 'peetprint',
                    dataKey: 'peet_prints',
                    elemID: 'peetprints',
                  },
                  {
                    notParamType: 'useragent',
                    dataKey: 'user_agents',
                    elemID: 'user-agents',
                  },
                ];

                mapping.forEach((elem) => {
                  let domElem = document.getElementById(elem.elemID);
                  if (params.type !== elem.notParamType) {
                    let i = 1;
                    const sorted = sortByVal(data[elem.dataKey]);
                    sorted.forEach((j) => {
                      domElem.insertRow(
                        i++
                      ).innerHTML = `<td><a href="/explore?type=${
                        elem.notParamType
                      }&by=${encodeURIComponent(j[0])}">· ${
                        j[0]
                      } <span class="font-black">(${
                        j[1]
                      } times)</span></a></td>`;
                    });
                  } else {
                    domElem.parentElement.style.display = 'none';
                  }
                });
              }
            });
        }
      };
      insert(window.data);
    </script>
  </body>
</html>
